diff options
| author | Factiven <[email protected]> | 2023-04-11 23:23:29 +0700 |
|---|---|---|
| committer | Factiven <[email protected]> | 2023-04-11 23:23:29 +0700 |
| commit | 1fcdd9f7d859b925bf92265f441655d5522e351c (patch) | |
| tree | 86391522f6fcc70d105f7e796a9f91d132ee4a29 /pages/anime/[...id].js | |
| parent | Initial commit (diff) | |
| download | moopa-1fcdd9f7d859b925bf92265f441655d5522e351c.tar.xz moopa-1fcdd9f7d859b925bf92265f441655d5522e351c.zip | |
initial commit
Diffstat (limited to 'pages/anime/[...id].js')
| -rw-r--r-- | pages/anime/[...id].js | 623 |
1 files changed, 623 insertions, 0 deletions
diff --git a/pages/anime/[...id].js b/pages/anime/[...id].js new file mode 100644 index 0000000..d42a394 --- /dev/null +++ b/pages/anime/[...id].js @@ -0,0 +1,623 @@ +import React, { useEffect, useState } from "react"; +import { AnimatePresence, motion as m } from "framer-motion"; +import { META } from "@consumet/extensions"; + +import Link from "next/link"; +import Layout from "../../components/layout"; +import Head from "next/head"; + +import { closestMatch } from "closest-match"; +import Content from "../../components/hero/content"; +import Image from "next/image"; + +export default function Himitsu({ + info, + slicedDesc, + color, + episodeList, + episode1, + judul, + subIndo, + epIndo, +}) { + const [isLoading, setIsloading] = useState(false); + const [showText, setShowtext] = useState(false); + const [title, setTitle] = useState(info.title.english || info.title.romaji); + const [load, setLoad] = useState(true); + const [Lang, setLang] = useState(true); + const [showAll, setShowAll] = useState(false); + + const [lastPlayed, setLastPlayed] = useState(null); + const episode = episodeList; + const epi1 = episode1; + + const maxItems = 3; + + function handleEnLang() { + setLang(true); + } + + function handleIdLang() { + setLang(false); + } + + // const { ref } = useParallax({ speed: 10 }); + + useEffect(() => { + const playedStr = JSON.parse(localStorage.getItem("lastPlayed")); + setLastPlayed( + playedStr?.filter((item) => item.title === info.title.romaji)[0]?.data + ); + function getBrightness(color) { + const rgb = color.match(/\d+/g); + return (299 * rgb[0] + 587 * rgb[1] + 114 * rgb[2]) / 1000; + } + + // set the text color based on the background color + function setTextColor(element) { + const backgroundColor = getComputedStyle(element).backgroundColor; + const brightness = getBrightness(backgroundColor); + if (brightness < 128) { + element.style.color = "#fff"; // white + } else { + element.style.color = "#000"; // black + } + } + + const elements = document.querySelectorAll(".dynamic-text"); + elements.forEach((element) => { + setTextColor(element); + }); + }, [color]); + + const handleStore = (props) => { + let existingData = JSON.parse(localStorage.getItem("recentWatch")); + if (!Array.isArray(existingData)) { + existingData = []; + } + const index = existingData.findIndex((item) => item.title === props.title); + if (index !== -1) { + existingData.splice(index, 1); + } + const updatedData = [props, ...existingData]; + localStorage.setItem("recentWatch", JSON.stringify(updatedData)); + }; + + if (!info) { + return; + } + + let episodeIndo = null; + if (epIndo < 17) { + episodeIndo = episode.slice(0, epIndo); + } else { + episodeIndo = episode; + } + + // console.log({ NEXT: subIndo }); + + // console.log(episodeIndo); + + // console.log(lastPlayed); + + function handleLoad() { + setLoad(false); + } + return ( + <> + <Head> + <title>{info.title?.english || info.title.romaji}</title> + <meta name="detail" content="Detail about the Anime" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + <link rel="icon" href="/c.svg" /> + </Head> + + <Layout navTop="text-white bg-[#121212] md:pt-0 md:px-0 bg-slate bg-opacity-40"> + <div className="text static flex w-screen flex-col justify-center pt-nav pb-10"> + <div className="pointer-events-none absolute top-0 left-0"> + <div className="brightness-90 bg-gradient-to-t from-[#121212] to-transparent"> + <img + // ref={ref} + src={info.cover || info.image} + className="h-[300px] w-screen object-cover brightness-75 mix-blend-darken" + /> + <div className="z-10 h-full drop-shadow-2xl bg-[#121212]" /> + </div> + </div> + {isLoading ? ( + <p>Loading cuy sabar...</p> + ) : info ? ( + <div className="flex flex-col items-center gap-10"> + <div className="flex w-screen flex-col gap-10 md:w-[70%]"> + <div className="z-40 flex flex-col gap-10 px-5 pt-[8rem] md:flex-row lg:mt-[5rem] lg:px-0"> + <div className="flex gap-10 md:h-[250px] md:w-52"> + <div className="flex h-[200px] w-52 bg-[#dadada50] md:h-[250px] md:w-full"> + {info.image && ( + <> + <div + style={{ + backgroundImage: `url(${info.image})`, + height: "100%", + width: "100%", + backgroundSize: "cover", + backgroundPosition: "center", + }} + // src={info.image} + className="h-[200px] w-[200px] md:h-[250px] bg-white shadow-md" + /> + </> + )} + </div> + + {/* MOBILE */} + <div className="flex w-full flex-col gap-5 lg:hidden "> + <h1 className="shrink-0 text-2xl font-semibold"> + {judul} + </h1> + <div className="flex w-[90%] flex-col gap-1"> + <div className="flex gap-2"> + <h1>Rate:</h1> + <p className="font-bold">{info.rating}%</p> + </div> + + <div className="flex w-[200px] gap-2"> + <h1>Format:</h1> + <p>{info.type}</p> + </div> + + <div className="flex gap-2"> + <h1>Status:</h1> + <p>{info.status}</p> + </div> + </div> + <div className="flex"> + {epi1 && epi1[0] ? ( + <Link + href={`/anime/watch/${epi1[0].id}/${info.id}`} + onClick={() => + handleStore({ + title: + info.title?.english || + info.title.romaji || + info.title.native, + description: info.description, + image: info.image, + id: info.id, + }) + } + > + <h1 className="flex cursor-pointer items-center gap-2 rounded-[20px] bg-[#ff9537] px-4 py-2 font-bold text-[#ffffff]"> + <svg + xmlns="http://www.w3.org/2000/svg" + width="13" + height="12" + fill="none" + viewBox="0 0 250 289" + > + <path + fill="#fff" + d="M249.734 144.5l-249 143.761V.741l249 143.759z" + ></path> + </svg>{" "} + WATCH + </h1> + </Link> + ) : ( + <h1 className="pointer-events-none flex items-center gap-2 rounded-[20px] bg-[#ff94378f] px-4 py-2 font-bold text-[#ffffffa5]"> + <svg + xmlns="http://www.w3.org/2000/svg" + width="13" + height="12" + className="fill-[#ffffff8d]" + viewBox="0 0 250 289" + > + <path d="M249.734 144.5l-249 143.761V.741l249 143.759z"></path> + </svg>{" "} + WATCH + </h1> + )} + </div> + </div> + </div> + + {/* PC */} + <div className="w-full flex-col gap-5 md:flex"> + <div className="hidden flex-col gap-5 lg:flex"> + <h1 className="text-4xl font-bold"> + {info.title?.english || + info.title.romaji || + info.title.native} + </h1> + <div className="flex gap-6"> + <div + className={`dynamic-text rounded-md px-2 font-karla font-bold`} + style={color} + > + {episode && episode.length} Episodes + </div> + <div + className={`dynamic-text rounded-md px-2 font-karla font-bold`} + style={color} + > + {info.releaseDate} + </div> + <div + className={`dynamic-text rounded-md px-2 font-karla font-bold`} + style={color} + > + {info.rating}% + </div> + <div + className={`dynamic-text rounded-md px-2 font-karla font-bold`} + style={color} + > + {info.type} + </div> + <div + className={`dynamic-text rounded-md px-2 font-karla font-bold`} + style={color} + > + {info.status} + </div> + <div + className={`dynamic-text rounded-md px-2 font-karla font-bold`} + style={color} + > + Sub | {subIndo === null ? "EN" : "EN/ID"} + </div> + </div> + </div> + <div + className={`hidden h-[140px] transition-all duration-300 overflow-y-hidden scrollbar-thin scrollbar-thumb-[#1b1c21] scrollbar-thumb-rounded-md hover:overflow-y-scroll hover:scrollbar-thumb-[#2e2f37] lg:block`} + > + <p + dangerouslySetInnerHTML={{ __html: info.description }} + className="mr-5" + /> + </div> + <div className="lg:hidden"> + <div + dangerouslySetInnerHTML={{ + __html: showText ? info.description : slicedDesc, + }} + ></div> + <button + onClick={() => setShowtext(!showText)} + className="font-rama font-bold text-white" + > + {showText ? " Show Less" : " Show More"} + </button> + </div> + </div> + </div> + + <div className=""> + <div className="flex gap-5 items-center"> + <div className="p-3 lg:p-0 text-3xl font-bold"> + Relations + </div> + {info.relations.length > maxItems && ( + <div + className="cursor-pointer" + onClick={() => setShowAll(!showAll)} + > + {showAll ? "show less" : "show more"} + </div> + )} + </div> + <div + className={`w-screen lg:w-full grid lg:grid-cols-3 justify-items-center gap-7 lg:pt-7 px-3 lg:px-4 pt-10 rounded-xl`} + > + {info.relations && + info.relations + .slice(0, showAll ? info.relations.length : maxItems) + .map((relation, index) => { + return ( + <Link + key={relation.id} + href={ + relation.type === "TV" || + relation.type === "OVA" || + relation.type === "MOVIE" || + relation.type === "SPECIAL" || + relation.type === "ONA" + ? `/anime/${relation.id}` + : `/manga/detail/id?aniId=${ + relation.id + }&aniTitle=${encodeURIComponent( + info.title?.english || + info.title.romaji || + info.title.native + )}` + } + className={`hover:scale-[1.02] scale-100 transition-transform duration-200 ease-out w-full ${ + relation.type === "MUSIC" + ? "pointer-events-none" + : "" + }`} + > + <div + key={relation.id} + initial={{ opacity: 0, y: 50 }} + animate={{ opacity: 1, y: 0 }} + exit={{ + opacity: 0, + y: -50, + transition: { duration: 0.5 }, + }} + transition={{ + duration: 0.8, + delay: index * 0.1, + }} + className="w-full shrink h-[126px] bg-secondary flex rounded-md" + > + <div className="min-w-[20%] bg-image rounded-l-md shrink-0"> + <img + src={relation.image} + alt={relation.id} + className="object-cover h-full w-full shrink-0 rounded-l-md" + /> + </div> + <div className="min-w-[80%] h-full grid px-3 items-center"> + <div className="text-action font-outfit font-bold"> + {relation.relationType} + </div> + <div className="font-outfit font-thin italic line-clamp-2"> + {relation.title.romaji} + </div> + <div className={``}>{relation.type}</div> + </div> + </div> + </Link> + ); + })} + </div> + </div> + + <div className="z-20 flex flex-col gap-10 p-3 lg:p-0"> + <div className="flex items-center gap-10"> + <h1 className="text-3xl font-bold">Episodes</h1> + <div className="flex items-center rounded-md"> + <button + onClick={handleEnLang} + className={ + Lang + ? `w-16 p-2 rounded-l-md bg-[#212121]` + : `w-16 p-2 rounded-l-md bg-[#171717] text-[#404040]` + } + > + EN + </button> + <div className="w-[1px] bg-white h-4" /> + <button + onClick={handleIdLang} + className={ + subIndo === null + ? `w-16 p-2 rounded-r-md bg-[#171717] text-[#404040] pointer-events-none` + : Lang + ? `w-16 p-2 rounded-r-md bg-[#171717] text-[#404040]` + : `w-16 p-2 rounded-r-md bg-[#212121]` + } + > + ID + </button> + </div> + </div> + <div className="flex h-[640px] flex-col gap-5 overflow-y-hidden scrollbar-thin scrollbar-thumb-[#1b1c21] scrollbar-thumb-rounded-full hover:overflow-y-scroll hover:scrollbar-thumb-[#2e2f37]"> + {episode && Lang ? ( + episode.map((episode, index) => { + const item = lastPlayed?.find( + (item) => item.id === episode.id + ); + // console.log(item); + return ( + <div key={index} className="flex flex-col gap-3"> + <Link + onClick={() => + handleStore({ + title: + info.title?.english || + info.title.romaji || + info.title.native, + description: info.description, + image: info.image, + id: info.id, + }) + } + href={`/anime/watch/${episode.id}/${info.id}/${ + item ? `${item.time}` : "" + }`} + className={`text-start text-xl ${ + item ? "text-[#414141]" : "text-white" + }`} + > + <p>Episode {episode.number}</p> + {episode.title && ( + <p + className={`text-[14px] ${ + item ? "text-[#414141]" : "text-[#b1b1b1]" + } italic`} + > + "{episode.title}" + </p> + )} + </Link> + <div className="h-[1px] bg-white" /> + </div> + ); + }) + ) : subIndo === null ? ( + <p>No Episodes Available</p> + ) : ( + <> + <div className="flex h-[640px] flex-col gap-5 overflow-y-hidden scrollbar-thin scrollbar-thumb-[#1b1c21] scrollbar-thumb-rounded-full hover:overflow-y-scroll hover:scrollbar-thumb-[#2e2f37]"> + {episodeIndo.map((episode, index) => { + return ( + <div key={index} className="flex flex-col gap-3"> + <Link + onClick={() => + handleStore({ + title: + info.title?.english || + info.title.romaji || + info.title.native, + description: info.description, + image: info.image, + id: info.id, + }) + } + href={`/anime/watch?title=${encodeURIComponent( + info.title?.romaji || info.title?.english + )}&id=${subIndo}&idInt=${info.id}&epi=${ + episode.number + }&epiTitle=${encodeURIComponent( + episode.title + )}&te=${epIndo}&sub=id`} + className="text-start text-xl" + > + <p>Episode {episode.number}</p> + <p className="text-[14px] text-[#b1b1b1] italic"> + "{episode.title}" (Sub Indonesia) + </p> + </Link> + <div className="h-[1px] bg-white" /> + </div> + ); + })} + </div> + </> + )} + </div> + </div> + </div> + <div className="w-screen md:w-[80%]"> + <Content + ids="recommendAnime" + section="Recommendations" + data={info.recommendations} + /> + </div> + </div> + ) : ( + <div className="flex h-screen flex-col items-center justify-center gap-10 pb-52 "> + <h1 className="scale-150 font-roboto text-6xl text-red-400"> + 404 + </h1> + <p className="text-4xl font-semibold">{`> Woops.. I think we don't have that Anime :(`}</p> + <Link className="pt-10 text-2xl" href="/search"> + Return to search + </Link> + </div> + )} + </div> + </Layout> + </> + ); +} + +export const getServerSideProps = async (context) => { + context.res.setHeader( + "Cache-Control", + "public, s-maxage=10, stale-while-revalidate=59" + ); + const { id } = context.query; + if (!id) { + return { + notFound: true, + }; + } + + const provider = new META.Anilist(); + + const [info, episodes] = await Promise.all([ + fetch(`https://api.moopa.my.id/meta/anilist/info/${id[0]}`).then((res) => + res.json() + ), + provider.fetchEpisodesListById(id[0]), + ]); + + let episodeList = episodes; + if (episodes.length === 0) { + const res = await fetch( + `https://api.moopa.my.id/anime/gogoanime/${ + info.title.romaji || info.title.english + }` + ); + const data = await res.json(); + const match = closestMatch( + info.title.romaji, + data.results.map((item) => item.title) + ); + const anime = data.results.filter((item) => item.title === match); + if (anime.length !== 0) { + const infos = await fetch( + `https://api.moopa.my.id/anime/gogoanime/info/${anime[0].id}` + ).then((res) => res.json()); + episodeList = infos.episodes; + } + } + + const ress = await fetch( + `https://ani-api-eight.vercel.app/kuramanime/search?query=${ + info.title.romaji || info.title?.english + }` + ); + + const yes = await ress.json(); + + // Clannad Fixer + function convertToClannad(text) { + const regex = /(?<!\w)CLANNAD(?!\w)/g; + return text.replace(regex, "Clannad"); + } + + const fixedTitle = convertToClannad(info.title.romaji); + + let epis = null; + let slug = null; + + if (!yes.error) { + // let anime = yes.list.filter((item) => item.title.includes(fixedTitle)); + let list = yes.list.map((item) => item.title); + const match = closestMatch(fixedTitle, list); + + const anime = yes.list.filter((item) => item.title === match); + + slug = anime[0]?.slug; + const inf = await fetch( + `https://ani-api-eight.vercel.app/kuramanime/anime/${slug}` + ); + + const dataInf = await inf.json(); + epis = dataInf.episode; + } + + const desc = info.description.slice(0, 150) + "..."; + const color = { backgroundColor: `${info.color}` }; + const epi1 = episodes.filter((epi) => epi.number === 1); + const title = info.title?.userPreferred || "No Title"; + + const MAX = 20; + + const oriJ = info.title?.english || info.title.romaji || info.title.native; + const judul = oriJ.length > MAX ? `${oriJ.substring(0, MAX)}...` : oriJ; + + return { + props: { + info: { + ...info, + title: { + ...info.title, + userPreferred: title, + }, + }, + slicedDesc: desc, + color, + episodeList, + episode1: epi1, + judul, + subIndo: slug, + epIndo: epis, + }, + }; +}; |